package org.aplikator.server.descriptor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.aplikator.client.data.Record;
import org.aplikator.client.descriptor.QueryParameter;
import org.aplikator.server.Context;
import org.aplikator.server.persistence.PersisterFactory;
import org.aplikator.server.persistence.PersisterTriggers;
import org.aplikator.server.query.QueryCompareExpression;
import org.aplikator.server.query.QueryCompareOperator;
import org.aplikator.server.query.QueryExpression;
import org.aplikator.shared.data.BinaryData;

public abstract class Entity extends ServerDescriptorBase implements Cloneable {

    private static final String TIMESTAMP_NAME = "UPDATE_TIMESTAMP";

    private Property<Integer> primaryKey;

    private Property<Date> timeStamp;

    private List<Property<? extends Serializable>> properties = new ArrayList<Property<? extends Serializable>>();

    private View defaultView;

    private PersisterTriggers trigger;

    protected Entity(String name, String readableName) {
        super(name);
        setLocalizationKey(readableName);

        Property<Integer> pk = new Property<Integer>(this.getId() + "." + "PRIMARY_KEY", Integer.class, 0, true, this);
        primaryKey = pk;

        Property<Date> ts = new Property<Date>(this.getId() + "." + TIMESTAMP_NAME, Date.class, 0, false, this);
        timeStamp = ts;
    }

    protected Entity(String name) {
        this(name, name);
    }

    protected Entity(String name, String tableName, String primaryKeyName) {
        this(name);
        map(tableName, primaryKeyName);

    }

    protected Entity map(String tableName, String primaryKeyName) {
        PersisterFactory.getPersister().registerEntity(this, tableName);
        PersisterFactory.getPersister().registerPrimaryKey(this, primaryKey, primaryKeyName);
        PersisterFactory.getPersister().registerTimestamp(this, timeStamp, TIMESTAMP_NAME);
        return this;
    }

    public List<Property<? extends Serializable>> getProperties() {
        return properties;
    }

    private <X extends Serializable> Property<X> addProperty(String name, Class<X> type, double size, boolean required) {
        Property<X> property = new Property<X>(this.getId() + "." + name, type, size, required, this);
        property.setLocalizationKey(this.getId() + "." + name);
        properties.add(property);
        PersisterFactory.getPersister().registerProperty(this, property, name);
        return property;
    }

    public Property<String> stringProperty(String name, int size, boolean required) {
        return addProperty(name, String.class, size, required);
    }

    public Property<String> stringProperty(String name, int size) {
        return stringProperty(name, size, false);
    }

    public Property<String> stringProperty(String name) {
        return stringProperty(name, 255);
    }

    public Property<String> textProperty(String name) {
        return addProperty(name, String.class, 0, false);
    }

    public Property<Boolean> booleanProperty(String name, boolean required) {
        return addProperty(name, Boolean.class, 0, required);
    }

    public Property<Boolean> booleanProperty(String name) {
        return booleanProperty(name, false);
    }

    public Property<BigDecimal> numericProperty(String name, double size, boolean required) {
        return addProperty(name, BigDecimal.class, size, required);
    }

    public Property<BigDecimal> numericProperty(String name) {
        return numericProperty(name, 10.2, false);
    }

    public Property<Integer> integerProperty(String name, boolean required) {
        return addProperty(name, Integer.class, 10.0, required);
    }

    public Property<Integer> integerProperty(String name) {
        return integerProperty(name, false);
    }

    public Property<Date> dateProperty(String name, boolean required) {
        return addProperty(name, Date.class, 0, required);
    }

    public Property<Date> dateProperty(String name) {
        return dateProperty(name, false);
    }

    public Property<BinaryData> binaryProperty(String name) {
        return addProperty(name, BinaryData.class, 0, false);
    }

    public <T extends Entity> Reference<T> referenceProperty(T referredEntity, String name) {
        return referenceProperty(referredEntity, name, name);
    }

    public <T extends Entity> Reference<T> referenceProperty(T referredEntity, String name, String mapping) {
        Reference<T> property = new Reference<T>(this.getId() + "." + name, referredEntity, this);
        property.setLocalizationKey(this.getId() + "." + name);
        properties.add(property);
        PersisterFactory.getPersister().registerReference(this, property, mapping, referredEntity);
        return property;
    }

    public <T extends Entity> Collection<T> collectionProperty(T referredEntity, String name) {
        return collectionProperty(referredEntity, name, name);
    }

    public <T extends Entity> Collection<T> collectionProperty(T referredEntity, String name, String mapping) {
        Collection<T> property = new Collection<T>(this.getId() + "." + name, referredEntity, this);
        property.setLocalizationKey(this.getId() + "." + name);
        properties.add(property);

        PersisterFactory.getPersister().registerReference(referredEntity, property, mapping, this);
        // DBTableColumn column = referredEntity.table.addColumn(name,
        // DataType.INTEGER, 0, false);
        // application.db.addRelation(getPrimaryKey().column.referenceOn(column));

        return property;
    }

    public <T extends Entity> Collection<T> reverseCollectionProperty(String name, T referredEntity, Reference<? extends Entity> referringColumn) {
        Collection<T> property = new Collection<T>(this.getId() + "." + name, referredEntity, this);
        property.setLocalizationKey(this.getId() + "." + name);
        properties.add(property);
        PersisterFactory.getPersister().registerReverseCollection(property, referringColumn);
        return property;
    }

    public Property<Integer> getPrimaryKey() {
        return primaryKey;
    }

    public Property<Date> getTimeStamp() {
        return timeStamp;
    }

    public void addIndex(String name, boolean unique, Property<?>... properties) {
        PersisterFactory.getPersister().registerIndex(this, name, unique, properties);
    }

    public PersisterTriggers getPersisterTriggers(Context ctx) {

        // TODO use context to select trigger?
        return this.trigger;
    }

    public Entity setPersistersTriggers(PersisterTriggers triggers) {
        this.trigger = triggers;
        return this;
    }

    /*
     * Clone is used for references to the same table (in Reference constructor
     * and method relate() )
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public Entity clone() throws CloneNotSupportedException {
        Entity retval = (Entity) super.clone();
        PersisterFactory.getPersister().registerEntityClone(this, retval);
        retval.primaryKey = this.primaryKey.clone();
        PersisterFactory.getPersister().registerPropertyClone(this.primaryKey, retval.primaryKey);
        retval.timeStamp = this.timeStamp.clone();
        PersisterFactory.getPersister().registerPropertyClone(this.timeStamp, retval.timeStamp);
        retval.properties = new ArrayList();
        for (Property sourceProp : properties) {
            Property targetProp = sourceProp.clone();
            retval.properties.add(targetProp);
            PersisterFactory.getPersister().registerPropertyClone(sourceProp, targetProp);
        }

        return retval;
    }

    /**
     * Create the default view for this entity. The view contains all properties
     * except References, Collections and BinaryData. It has simple form with
     * VerticalPanel, containing default widgets for all properties, obtained
     * from Property.widget() method
     *
     * @see Property
     * @return The default view
     */

    public synchronized View view() {
        if (defaultView == null) {
            defaultView = initDefaultView();
        }
        return defaultView;
    }

    protected View initDefaultView(){
        View retval = new View(this);
        Form form = new Form();
        Panel layout = Panel.column();

        for (Property<? extends Serializable> prop : properties) {
            if (!((prop instanceof Reference) || (prop instanceof Collection) || (prop.getType().equals(BinaryData.class)))) {
                retval.addProperty(prop);
            }
            layout.add(prop.widget());
        }
        form.setLayout(layout);
        retval.setForm(form);
        return retval;
    }

    public View inheritanceView(View masterView, Property<String> discriminator, Enum<?> value) {
        View view = new View(masterView, value.name());
        view.setPersistersTriggers(new Entity.SubPersisterTriggers(discriminator, value));
        view.setQueryGenerator(new Entity.SubQueryGenerator(discriminator, value));
        return view;
    }

    protected static class SubPersisterTriggers extends PersisterTriggers.Default {
        private String typTitulu;
        Property<String> discriminator;

        protected SubPersisterTriggers(Property<String> discriminator, Enum<?> typTitulu) {
            this.typTitulu = typTitulu.name();
            this.discriminator = discriminator;
        }

        @Override
        public Record beforeCreate(Record record, Context ctx) {
            discriminator.setValue(record, typTitulu);
            return record;
        }
    }

    protected static class SubQueryGenerator extends QueryGenerator.Empty {
        private String typTitulu;
        Property<String> discriminator;

        protected SubQueryGenerator(Property<String> discriminator, Enum<?> typTitulu) {
            this.typTitulu = typTitulu.name();
            this.discriminator = discriminator;
        }

        @Override
        public QueryExpression createWhere(QueryParameter[] queryParameters, Context ctx) {
            return new QueryCompareExpression<String>(discriminator, QueryCompareOperator.IS, typTitulu);
        }

    }

}
